# -*- coding: iso-8859-1 -*-
import paths
import pygame, time,math
from pygame.locals import *
import pygame.locals as pygamelocals
import random
import pickle

from gb_exceptions import *
import sys
import threading

""" 
******************************************
script objects:
******************************************
These are the objects that the user interacts with in scripts.
"""

class scriptobject(object):
    def __init__(self):

        pass

class scriptgame(scriptobject):
    """The object used in scripting language for a *game*
        - all in-game data should be stored here so 
        games can be saved easily"""
    def __init__(self,game,save_game,gamesounds):
        self.vars = {}
        self.score = 0
        self.get_fps = None # Set to be the clock's fps function 
                            # at runtime
        self.save_game = save_game
        self.gamesounds = gamesounds
        self.music_background = pygame.mixer.music
        pass

    def change_game_screen(self,gs,savestate=False):
        raise game_event(["changeGameScreen",gs,savestate])
        
    def save_game(self,gamestate):
        #TODO
        output = open('savedgame.pkl','wb')
        pickle.dump(gamestate,output)
        output.close()
        
    def load_game(self):
        #TODO
        pkl_file = open('savedgame.pkl','rb')
        savedgame = pickle.load(pkl_file)
        pkl_file.close()
         
        return savedgame
        
    def exit(self):
        raise exit_game("Quit from method .exit() of game")

class script_gameobject_dict(scriptobject):
    """Simple sequence object for game objects
        New Interface 24th May 2008"""
    def __init__(self,dictionary):
        self.dictionary = dictionary
        
        # Simple generator for itterating over objects
        def gen():
            keys = self.dictionary.keys()
            for k in keys:
                yield dictionary[k].scriptvar

        self.generator = gen


    def __getitem__(self, index):
        return self.dictionary[index].scriptvar

    def __iter__(self):
        return self.generator()

    def keys(self):
        return self.dictionary.keys()
        
    def items(self):
        return self.dictionary.items()
        
    def values(self):
        return self.dictionary.values()
    



class scriptgamescreen(scriptobject):
    """The object used in scripting language for a *game screen*
        - all in-game data should be stored here so 
        games can be saved easily"""
    def __init__(self,gamescreen):
        self.vars = {}
        self.__gameobjects = gamescreen.gameobjects
        self.__gamescreen = gamescreen
        self._vx,self._vy,self.y,self.x = 0,0,0,0
        self.width = self.__gamescreen.gamescreen.dimensions[0]
        self.height = self.__gamescreen.gamescreen.dimensions[1]
        try:
            self.color = gamescreen.gamescreen.color
        except: pass
        
        # New interface to gameobjects 24th May 08
        self.gameobjects=script_gameobject_dict(self.__gameobjects)
        
    def change_dimension(self,xdim,ydim):
        """Changes the screen dimensions"""
        self.width = xdim
        self.height = ydim
        
    def get_object(self,name):
        """Returns the gameobject script interfaces for active game objects"""
        return self.__gameobjects[name].scriptvar

    def add_object(self,obj,name):
        """Adds a new game object instance to a gamescreen """
        o = self.__gamescreen.add_object_instance(obj,name)

        return o.scriptvar

    def remove_object(self,name):
        """removes a game object instance"""
        o = self.__gamescreen.remove_object_instance(name)
        

    def move_gamescreen(self,target,gradual=False,maxspeed=10,acceleration=2):
        """Tells the gamescreen to move (gradually if desired) across the
            rendering window, for example to keep a characer centred on the
            """
        if gradual:
            if self.x + 400 < target[0]:
                if self._vx < maxspeed:
                    self._vx += acceleration
            else:
                if self._vx > - maxspeed:
                    self._vx -=acceleration
            if self.y + 300 < target[1]:
                if self._vy < maxspeed:
                    self._vy += acceleration
            else:
                if self._vy > -maxspeed:
                    self._vy -= acceleration

        if abs(self._vx) > abs(self.x + 400 - target[0]):
            gradual = False
        if abs(self._vy) > abs(self.y + 300 - target[1]):
            gradual = False

        if not gradual:
            # TODO make this vary with screen resolution
            self._vx =  target[0] - self.x - 400
            self._vy =  target[1] - self.y - 300



        self.x = self.x + self._vx
        self.y = self.y + self._vy

        if self.x < 0: self.x = 0
        if self.y < 0: self.y = 0
        if self.x > self.width - 800:
            self.x = self.width - 800
        if self.y > self.height - 600:
            self.y = self.height - 600

    def get_collisions(self,obj1,obj2=[]):
        """Find which objects are colliding
            You can provide list(s) of game objects to check"""
        if not type(obj1) == list:
            obj1 = [obj1,]
        if not type(obj2) == list:
            obj2 = [obj2,]

        objects = obj1 + obj2

        xaxis = []
        yaxis = []

        gameobjects = self.__gamescreen.gameobjects

        for i in xrange(len(objects)):
            try:
                obj = gameobjects[objects[i]].scriptvar
                xaxis.append((obj.x,i,1))
                xaxis.append((obj.x + obj.width,i,-1))
                yaxis.append((obj.y,i,1))
                yaxis.append((obj.y+obj.height,i,-1))
            except: pass


        xaxis.sort()
        yaxis.sort()

        xdict={}
        ydict={}

        possibles = []

        matches = []
        
        for x,i,val in xaxis:
            if val == 1:
                possibles = possibles + [(i,j) for j in xdict.keys() if ( xdict[j] == 1 and not i==j)]
            xdict[i] = xdict.get(i,0) + val

        new_pos = []

        for y,i,val in yaxis:
            if val == 1:
                new_pos = new_pos + [(i,j) for j in ydict.keys() if ydict[j] == 1 and not i==j]

            ydict[i] = ydict.get(i,0) + val

        # Highly optimised loop
        for (j,k) in new_pos:
            if not (j,k) in possibles:
                if not (k,j) in possibles:
                    continue
            matches.append((j,k))
            matches.append((k,j))


        return [ (objects[i],objects[j]) for (i,j) in matches]


    def check_collisions(self,obj1,obj2):
        """Checks a collision between objects
           You can also provide lists of game objects to check"""
        if type(obj1) == list and type(obj2) == list:

            # New, Lazy evaluated version
            return any( (self.check_collisions(o1,o2) for o1 in obj1 for o2 in obj2) )
            """
            for o1 in obj1:
                for o2 in obj2:
                    if self.check_collisions(o1,o2):
                        return True
            return False
            """

        if type(obj1) == list:
            for o1 in obj1:
                if self.check_collisions(o1,obj2):
                    return True
            return False

        if type(obj2) == list:
            for o2 in obj2:
                if self.check_collisions(obj1,o2):
                    return True
            return False
            
        o1 = self.__gamescreen.gameobjects[obj1]
        o2 = self.__gamescreen.gameobjects[obj2]

        w1 = o1.surface.get_width()
        w2 = o2.surface.get_width()

        h1 = o1.surface.get_height()
        h2 = o2.surface.get_height()

        x1 = o1.scriptvar.x
        x2 = o2.scriptvar.x

        y1 = o1.scriptvar.y
        y2 = o2.scriptvar.y

        # TODO Only do this since the area is a rectangle - has to be changed

        if x1 < x2 + w2:
            if x1 + w1> x2:
                if y1 < y2 + h2:
                    if y1+ h1 > y2:
                        return True
                else:
                    if y2 > y1 + h1:
                        return True
        else:
            if x2 + w2 > x1:
                if y1< y2 + h2:
                    if y1 + h1 > y2:
                        return True
                else:
                    if y2 < y1 + h1:
                        return True
        return False




class scriptgameobj(scriptobject):
    """The object used in scripting language for a *game object*
        - all in-game data should be stored here so 
        games can be saved easily"""

    

    def __init__(self,go,gamescreenobj):
        self.gamescreenobj = gamescreenobj
        self.vars = {}
        self.draw_text = go.drawtext
        self.clear = go.clear
        self.resize = go.resize
        self.color=(0,0,0)
        self.draw_sprite = go.drawsprite
        self.z=0
        self.timers=[]
        self.width = 5
        self.height = 5

        self.path = script_path(self)
        self.__gameobject = go

    name = property( \
        fget=lambda x: x.gamescreenobj.get_game_object_name(x.__gameobject),
        fset=lambda x,new_name: x.gamescreenobj.set_game_object_name(x.__gameobject,new_name))


    def move_velocity(self):
        """Move This Object with a pre-defined Velocity"""
        # Find time elapsed
        time_elapsed = self.gamescreenobj.time_since_last_call / 1000.
        # Move object with a velocity
        self.x += self.vx * time_elapsed
        self.y += self.vy * time_elapsed

    def set_timer(self,delay,message=""):
        """Adds a timer event"""
        self.timers.append((time.time()+delay,message))
        self.timers.sort()

    def destroy(self):
        """Destroys a game object - removes it from the list"""
        self.gamescreenobj.to_delete_scriptgameobjs.append(self)

    def move_towards(self, x, y, speed, keep_distance = None):
        """Moves an object towards a position"""
        xdiff = (x - (self.x + 0.5 * self.width ) )
        ydiff = (y - (self.y + 0.5 * self.height) )
        length = math.sqrt(xdiff*xdiff + ydiff*ydiff)
        if keep_distance is not None and keep_distance > length:
            return
        if length > 0.00001:
            self.vx = xdiff * speed / float(length)
            self.vy = ydiff * speed / float(length)
        self.move_velocity()

    def move_to_intercept(self,speed,target=None,x=None,y=None,vx=None,vy=None):
        """Move This object to intercept another one
        - may supply a target or alternatively the x and y coordinates of the
        target object along with the velocity of that object"""
        if speed < 0.000000001:
            return

        if x == None:
            x = target.x + 0.5 * target.width
            y = target.y + 0.5 * target.height
            vx = target.vx
            vy = target.vy

        target_speed = math.sqrt(vx*vx + vy*vy)

        xdiff = (x - (self.x + 0.5 * self.width ) )
        ydiff = (y - (self.y + 0.5 * self.height) )
        distance = math.sqrt(xdiff*xdiff + ydiff*ydiff)

        relative = target_speed / distance
        
        aim_at = (xdiff + relative*vx , ydiff + relative*vy)

        length = math.sqrt(aim_at[0]*aim_at[0] + aim_at[1]*aim_at[1])
        if length > 0.000000001:
            self.vx = speed * aim_at[0]/length
            self.vy = speed * aim_at[1]/length

        self.move_velocity()

        
        # NOT DONE

        
        


class script_path(scriptobject):
    """Path Object for Scripting"""
    def __init__(self,script_game_object):
        self.__script_game_obj=script_game_object
        self.__path = None
        self.__t = 0.
        self.__speed = 1.
        self.__localspeed = 0.


    def set_speed(self,speed=None):
        """Sets the speed along the path in pixels/second"""
        if not speed is None:
            self.__speed = speed
        pathlength = self.__path.length()
        self.__localspeed = self.__speed / pathlength

    def get_speed(self):
        """Return the speed along the path"""
        return self.__speed

    def follow_path(self,loop=False,speed=None,t=None):
        """If t is specified it should be between 0 and 1"""
        if not speed is None:
            self.__speed = speed

        if not t is None:
            # Go to User Specified point
            self.__t = float(t)
            time_elapsed = 0.
        else:
            # Nasty reverse object path to find the time taken
            time_elapsed = self.__script_game_obj.gamescreenobj.time_since_last_call / 1000.
            self.__t += time_elapsed * self.__localspeed

        if loop:
            self.__t = self.__t % 1.

        # TODO: find update path rather than calculating the whole thing
        (x,y) = self.__path.get_position(t=self.__t)

        if not time_elapsed == 0.:
            self.__script_game_obj.vx = (x - self.__script_game_obj.x) / time_elapsed
            self.__script_game_obj.vy = (y - self.__script_game_obj.y) / time_elapsed

        self.__script_game_obj.x = x
        self.__script_game_obj.y = y

    def get_position(self,t):
        """Get the current position from t where 0<=t<=1"""
        return self.__path.get_position(t=t)
            

    def new_path(self,style="linear"):
        """Create a new path - style is one of ["linear"]"""

        if style == "linear":
            self.__path = paths.linear_bezier()
        else:
            raise gb_scripting_exception(log="No Path Style '%s'"%str(style),orig_except=inst)

    def add_point(self,x,y):
        """Add a point"""
        try:
            return self.__path.add_point(x,y)
        except Exception, inst :
            raise gb_scripting_exception(log="No Existing path found",orig_except=inst)

    def start_path(self,speed=None):
        """Start following the path from the beginning"""
        if not speed is None:
            self.set_speed(speed)
        self.__t = 0
        self.follow_path()
        pass
        
    def set_point(self,point,x,y):
        self.__path.set_point(point,x,y)
        self.set_speed()


class script_mouse(object):
    """Wrapper For pygame.mouse"""
    __mouse = pygame.mouse

    x = property(fget=lambda x:pygame.mouse.get_pos()[0])
    """mouse.x - wrapper for x value of the mouse"""
    y = property(fget=lambda x:pygame.mouse.get_pos()[1])
    """mouse.y - wrapper for y value of the mouse"""

    def __init__(self):
        pass

    # Pass these through to the pygame.mouse object
    get_pressed = __mouse.get_pressed
    get_pos = __mouse.get_pos
    get_rel = __mouse.get_rel
    set_pos = __mouse.set_pos
